package edu.ucsb.cs56.W12.chatserver.model;
import edu.ucsb.cs56.W12.chatserver.controller.ServerController;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;

/**
 * Server class represent the server model
 * @author Peng Wang and Andro Stotts
 * @version 0.1
 */
public class Server{
	private final int port = 8888;
	private boolean isServerStart;
	private ServerSocket ss;
	private String serverMsg;
	private String serverMsgPrefix;
	private String clientMsgPrefix;
	private ArrayList<Client> clients;
	private static Server server = null;
	
	/**
	 * default constructor
	 */
	private Server(){
		isServerStart = false;
		ss = null;
		serverMsg = "";
		serverMsgPrefix = "[Server Message] ";
		clientMsgPrefix = "[client Message] ";
		clients = new ArrayList<Client>();
	}
	
	/**
	 * get the server object
	 * @return the server object
	 */
	public static Server getServer(){
		if(server == null)
			server = new Server();
		return server;
	}
	
	/**
	 * start the server
	 * @return true when server starts successfully, otherwise returns false
	 */

	public void start(){
		try{
			//set the server's port
			ss = new ServerSocket(port);
			isServerStart = true;
			//create a Connection object on a separate thread for waiting new connections
			Connection c = new Connection();
			new Thread(c).start();
			//set the server message
			serverMsg = serverMsgPrefix + "Server has been started and waiting for client connections\n";
		} catch(IOException ex){
			serverMsg = serverMsgPrefix + "Server started failed";
		}
	}
	
	/**
	 * close the server
	 * @return true when server closes successfully, otherwise returns false
	 */
	public void close(){
		try {
			//close the server socket
			ss.close();
			//set the server status to be false
			isServerStart = false;
			//set the server message
			serverMsg = serverMsgPrefix + "Server shut down successfully\n";
		} catch (IOException e) {
			serverMsg = serverMsgPrefix + "Problem occured when server shutting down\n";
		}
	}
	
	/**
	 * get the server status
	 * @return true when server is started, otherwise returns false
	 */
	public boolean getServerStatus(){
		return isServerStart;
	}
	
	/**
	 * get the server message
	 * @return server message
	 */
	public String getServerMsg(){
		return serverMsg;
	}
	
	/**
	 * inner class Client only provide service to the outer class
	 * @author Peng Wang and Andro Stotts
	 * @version 0.1
	 */
	class Client implements Runnable{
		private Socket s;
		private SocketAddress ip;
		private boolean isClientConnect;
		private DataInputStream dis = null;
		private DataOutputStream dos = null;
		private ServerController controller = ServerController.getController();
		
		/**
		 * Constructor
		 * @param s socket
		 */
		public Client(Socket s){
			//save the client socket
			this.s = s;
			//get the client ip address
			ip = s.getRemoteSocketAddress();

			try {
				//get input and output stream
				this.dis = new DataInputStream(s.getInputStream());
				this.dos = new DataOutputStream(s.getOutputStream());
				//set the connection status to be true
				this.isClientConnect = true;
				//set the connection message
				controller.displayMsg(serverMsgPrefix + "a client has connected from IP: " + ip + '\n');
			} catch (IOException e) {
				e.printStackTrace();
			} 
		}
		
		/**
		 * send a message to the client
		 * @param msg message to be sent to the client
		 */
		public void sendMsg(String msg){
			try {
				dos.writeUTF(msg);			
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		/**
		 * waits for message, and broadcasts it
		 * on failure it removes client from list and it closes all streams
		 */
		public void run() {
			try{
				while(isClientConnect && isServerStart){
					//readUTF always waiting unless there is something has been inputed
					String msg = dis.readUTF();
					controller.displayMsg(clientMsgPrefix + msg + '\n');
					//broadcast the message to all clients
					for(int i = 0; i < clients.size(); i++){
						Client c = clients.get(i);
						c.sendMsg(msg);
					}
				}
			} catch(Exception e){
				//remove the current client from the list on server
				clients.remove(this);
				controller.displayMsg(serverMsgPrefix + "a client has disconnected\n");
			} finally{
				try {
					if(dos != null)
						dos.close();
					if(dis != null)
						dis.close();
					if(s != null)
						s.close();				
				} catch (IOException e1) {
					controller.displayMsg("server down\n");
				}
			}
		}
	}
	
	
	/**
	 * Connection class is used for listening new client connections
	 * on a separate thread
	 * @author PengWang and Andro Stotts
	 * @version 0.1
	 */
	class Connection implements Runnable{
		public void run() {
			while(isServerStart){
				try {
					Socket s = ss.accept();
					Client client = new Client(s);
					clients.add(client);
					new Thread(client).start();
				} catch (IOException e) {
					serverMsg = "connection failed\n";
				}
			}
		}
	}
}
